package com.lishem.rpc.client.cluster.impl;

import com.lishem.rpc.client.channel.ProviderService;
import com.lishem.rpc.client.cluster.ClusterStrategy;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 随机预热权重策略
 */
public class RandomWarnupClusterStrategyImpl implements ClusterStrategy {

    /**
     * 预热时间， 默认设定为10分钟， 超过10分钟不进行预热
     */
    private int DEFAULT_WARMUP = 10 * 60 * 1000;

    @Override
    public ProviderService select(List<ProviderService> serviceRoutes) {
        // 总的可用服务数量
        int length = serviceRoutes.size();
        // 假设每个服务都有相同的权重
        boolean sameWeight = true;
        // 服务权重记录数组
        int[] weights = new int[length];
        // 获取第一个服务的权重， 用于判断比较
        int firstWeight = getWeight(serviceRoutes.get(0));
        weights[0] = firstWeight;
        // 记录所有服务的权重
        int totalWeight = firstWeight;

        // 遍历所有服务的节点， 计算所有权重， 并标记所有权重是否平均分配
        for (int i = 1; i < length; i++) {
            // 计算服务节点的权重
            int weight = getWeight(serviceRoutes.get(i));
            // 记录服务节点的权重
            weights[i] = weight;
            // 记录所有服务节点的权重
            totalWeight += weight;

            if (sameWeight && weight != firstWeight) {
                sameWeight = false;
            }
        }

        // 如果权重不是平均分配， 则优先取较大的权重
        if (totalWeight > 0 && !sameWeight) {
            // 先从总权重中获取一个随机值
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // 用随机值和节点的权重做遍历比较，较大的权重节点， 为负数时， 会优先取出
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return serviceRoutes.get(i);
                }
            }
        }

        //  如果所有节点都均等， 则直接从集合中随机取出
        return serviceRoutes.get(ThreadLocalRandom.current().nextInt(length));
    }

    /**
     * 获取服务的权重（会计算预热权重）
     * @param providerService
     * @return
     */
    protected int getWeight(ProviderService providerService) {
        int weight = providerService.getWeight();
        if (weight > 0) {
            long timestamp = providerService.getRegisterTime();
            if (timestamp > 0L) {
                int uptime = (int) (System.currentTimeMillis() - timestamp);
                if (uptime > 0 && uptime < DEFAULT_WARMUP) {
                    weight = calculateWarmupWeight(uptime, DEFAULT_WARMUP, weight);
                }
            }
        }
        return weight >= 0 ? weight : 0;
    }

    /**
     * 计算预热权重
     * @param uptime
     * @param warmup
     * @param weight
     * @return
     */
    private int calculateWarmupWeight(int uptime, int warmup, int weight) {
        // 计算规则： 启动时间（预热时间/实际权重）
        int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
        // 权重必须大于1， 并且预热的权重不能超过原有的权重值
        return ww < 1 ? 1 : (ww > weight ? weight : ww);
    }
}
